package com.songjg.demo.syslog;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Description: 使用aop进行统一日志处理
 * @Author: songjg
 * @Date: 2020-3-5 10:26
 */
@Aspect
@Component
@Slf4j

public class SystemLogAop{

    private static final String UNKNOWN = "unknown";
    private static final String DEFAULT_ADDR = "172.0.0.1";
    private static final String ERROR_INFO = " [ 异常信息： {} ]";
    /**配置时间*/
    private  ThreadLocal<Long> timeTreadLocal;
    /**
     * 指定切点
     * 匹配 com.songjg.demo.controller包及其子包下面的所有类的方法
     */
    @Pointcut("execution(public * com.songjg.demo.controller.*.*(..))")
    public void sysLog() {

    }

    /**
     * 获取 ip
     * @param request
     * @return
     */
    private String getAddIp(HttpServletRequest request) {
        if (request != null) {
            String ip = request.getHeader("x-forwarded-for");
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy - Client - IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
            if (StringUtil.isNotEmpty(ip) && !UNKNOWN.equalsIgnoreCase(ip)) {
                ip = ip.split(",")[0].trim();
            }
            if (DEFAULT_ADDR.equalsIgnoreCase(ip)) {
                ip = DEFAULT_ADDR;
            }
            return ip;
        }
        return "获取ip失败！！！";
    }

    /**
     * 前置通知，在方法之前执行
     * @param joinPoint
     */
    @Before("sysLog()")
    public void  doBefore(JoinPoint joinPoint){
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //设置时间
        timeTreadLocal = new ThreadLocal<>();
        timeTreadLocal.set(System.currentTimeMillis());
        //过滤不能序列的类型
        Object[] args = joinPoint.getArgs();
        Stream<?> stream = ArrayUtils.isEmpty(args) ? Stream.empty() : Arrays.stream(args);
        List<Object> logArgs = stream
                .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                .collect(Collectors.toList());
        try {
            log.info("[ 执行方法： {}.{}() ]", joinPoint.getTarget().getClass().getName(), joinPoint.getSignature().getName());
            //获取请求信息
            log.info("[ ip 地址： {} ]", getAddIp(request));
            log.info("[ 请求地址： {} ]", request.getRequestURL().toString());
            log.info("[ HTTP类型： {} ]", request.getMethod());
            log.info("[ 请求参数： {} ]", JSON.toJSONString(logArgs));
        } catch (Exception e) {
            log.error("[ @Before : 发生异常！！！]");
            log.error(ERROR_INFO,e.getMessage());
        }
    }

    /**
     * 后置增强：切点方法返回后执行，方法正常退出时，输出执行的输出结果
     * @param target
     */
    @AfterReturning(returning = "target", pointcut = "sysLog()")
    public void doAfterReturning(Object target) {
        try {
            log.info("[ 返回结果： {} ]", JSONObject.toJSONString(target));
        } catch (Exception e) {
            log.error("[ @AfterReturning : 发生异常！！！]");
            log.error(ERROR_INFO,e.getMessage());
        }
    }

    /**
     * 在切点方法之后执行（不管抛出异常或者正常退出都会执行），打印方法执行消耗的时间
     */
    @After("sysLog()")
    public void doAfter() {
        try {
            log.info("[ 共耗时长： {} ms ]", System.currentTimeMillis() - timeTreadLocal.get());
        } catch (Exception ex) {
            timeTreadLocal.remove();
        }
    }

}
